The Non-Client Area |
The title bar is placed at the top of a window application. It typically has three buttons on the right: minimize, maximize and close. Some applications require the customization of the title bar and borders of the window, to do this you need to process several messages of the Non-client area. The figure below shows the Client Area and the Non-Client Area. La barra de título es localizado en la parte superior de una aplicación de ventana. Esta típicamente tiene tres botones en la derecha: minimizar, maximizar y cerrar. Algunas aplicaciones requieren personalizar la barra de título y los bordes de la ventana, para hacer esto usted necesita procesar varios mensajes del área de No-cliente. La figura de abajo muestra el Área del Cliente y el Área del No-Cliente. |
Tip |
Before processing a Non-Client Area message, you need to be sure that you know how to handle this message appropriately. In worst case scenario, you must to call ::DefWindowProc(...). To reduce flickering use Double Buffering: paint first on a bitmap and then transfer the bitmap to the Non-Client Area. Paint only those areas that require painting, for instance, if a button is pressed, you must paint only that button using a proper clipping region. Antes de procesar un mensaje del Área del No-Cliente, usted necesita estar seguro que conoce como procesar este mensaje en forma apropiada. En el peor de los escenarios, usted debe llamar ::DefWindowProc(...). Para reducir el parpadeo use Buffer Doble: pinte primero en un bitmap y entonces transfiera el bitmap al Área del No-Cliente. Pinte solamente esas áreas que requieren pintado, por ejemplo, si un botón es presionado, usted debe pintar solamente ese botón usando una región de clipping apropiada. |
WM_NCCREATE |
This is one of the first messages that an application receives. To preserve the normal behavior of the application, you must call ::DefWindowProc(...) during Window_NcCreate(...). Este es uno de los primeros mensajes que una aplicación recibe. Para preservar el comportamiento normal del programa, usted debe llamar ::DefWindowProc(...) durante Window_NcCreate(...). |
WM_NCCALCSIZE |
It is sent to compute the size of the Window and the size of the Client Area. To preserve the normal behavior of the application, you must call ::DefWindowProc(...) during Window_NcCalcSize(...). Este es enviado para calcular el tamaño de la ventana y el tamaño del Área del Cliente. Para preservar el comportamiento normal del programa, usted debe llamar ::DefWindowProc(...) durante Window_NcCalcSize(...). |
WM_NCPAINT |
It is sent to when the Non-Client Area needs to be painted. This is NOT similar to WM_PAINT because Microsoft Windows may require painting the Non-Client Area during processing of other WM_NC* messages. The code below shows how to handle this message, you should store the HRGN de la clipping region to preserve the same clipping during processing of other messages that require painting the Non-Client Area. To preserve the normal behavior of the application, you must call ::DefWindowProc(...) during Window_NcPaint(...). To paint during the processing of other WM_NC* messages, you must use something similar to the code shown below. Este es enviado cuando el Área del No-Cliente necesita pintarse. Este NO es similar a WM_PAINT porque Microsoft Windows puede requerir pintar el Área del No-Cliente durante procesamiento de otros mensajes WM_NC*. El código de abajo muestra como procesar este mensaje, usted debe almacenar el HRGN de la región de Clipping para preserva el mismo recorte durante el procesamiento de otros mensajes que también requieran pintar el Área del No-Cliente. Para preservar el comportamiento normal del programa, usted debe llamar ::DefWindowProc(...) durante Window_NcPaint(...). Para pintar durante el procesamiento de otros mensajes WM_NC*, usted debe usar algo similar al código mostrado debajo. |
Program.cpp |
void Program::Window_NcPaint(Win::Event& e) { //____________________________ Get an HDC HDC hDC = NULL; HRGN hRgn = NULL; if (e.wParam == 1) { hRgn = ::CreateRectRgn(rectWindow.left, rectWindow.top, rectWindow.right, rectWindow.bottom); hDC = ::GetDCEx(hWnd, hRgn, DCX_WINDOW|DCX_CACHE|DCX_INTERSECTRGN|DCX_LOCKWINDOWUPDATE); } else { hDC = ::GetDCEx(hWnd, (HRGN)e.wParam, DCX_WINDOW|DCX_CACHE|DCX_INTERSECTRGN|DCX_LOCKWINDOWUPDATE); } if (hDC == NULL) return; ... ::ReleaseDC(hWnd, hDC); ::RedrawWindow(hWnd, &rectWindow, (HRGN)e.wParam, RDW_UPDATENOW); } |
WM_NCLBUTTONDOWN and WM_NCLBUTTONUP |
These messages handle any click over the Non-Client area, such as clicking on the Close button. These messages are also used during the resizing of the window when the user drags the border of the window. To preserve the normal behavior of the application, you must call ::DefWindowProc(...) during this two messages. If you want to change the appearance of any button in the Non-Client Area, when the user clicks the buttons, you must paint part of the Non-Client Area during the processing of these messages. Estos mensajes se encargan de procesar cualquier clic en el Área del No-Cliente, tal como hacer clic en el botón de Cerrar. Estos mensajes también son usados durante el cambio del tamaño de la ventana cuando se arrastra el borde la ventana. Para preservar el comportamiento normal del programa, usted debe llamar ::DefWindowProc(...) durante estos dos mensajes. Si usted quiere cambiar la apariencia de los botones en el Área de No-Cliente, cuando el usuario hace clic con el ratón, usted debe pintar parte del Área del No-Cliente durante estos mensajes. |
WM_NCMOUSEMOVE |
These messages handle the Resizing cursor in the border of the window, the resizing of the window using the borders, and any appearance of the buttons of the Non-Client Area when the mouse is moved. To preserve the normal behavior of the application, you must call ::DefWindowProc(...) during Window_NcMouseMove(...). If you want to change the appearance of any element in the Non-Client Area, when the user moves the mouse, you must paint part of the Non-Client Area during the processing of this message. Este mensaje se encarga del cursor de Cambio de Tamaño en el borde de la ventana, del cambio de tamaño de la ventana usando los bordes y de la apariencia de los botones del Área el No-Cliente cuando el ratón se mueve. Para preservar el comportamiento normal del programa, usted debe llamar ::DefWindowProc(...) durante Window_NcMouseMove(...). Si usted quiere cambiar la apariencia de cualquier elemento en el Área de No-Cliente, cuando el usuario mueve el ratón, usted debe pintar parte del Área del No-Cliente durante este mensaje. |
WM_NCACTIVATE |
This message is received when the Window becomes active or inactive. To preserve the normal behavior of the application, you must call ::DefWindowProc(...) during Window_NcActive(...). You must paint the entire Non-Client Area during the processing of this message. Este mensaje es recibido cuando la Ventana se activa o se desactiva. Para preservar el comportamiento normal del programa, usted debe llamar ::DefWindowProc(...) durante Window_NcActive(...). Usted debe pintar toda el Área del No-Cliente durante el procesamiento de este mensaje. |
Problem 1 |
Create a Wintempla Window Application called Different. After creating the program, open Wintempla and check the following events in the main window: NcActivate, NcCalcSize, NcCreate, NcHitTest, NcLButtonDown, NcLButtonUp, NcMouseMove, NcPaint, Paint and ActivateApp. For this problem you will need the icons shown below (the recommend size for these icons is 16x16). You can add icons to your project using the Resource View. Cree una Aplicación de Ventana de Wintempla llamada Different. Después de crear el programa, abra Wintempla y marque los siguientes eventos en la ventana principal: NcActivate, NcCalcSize, NcCreate, NcHitTest, NcLButtonDown, NcLButtonUp, NcMouseMove, NcPaint, Paint y ActivateApp. Para este problema usted necesitará los iconos mostrados debajo (el tamaño recomendado para estos iconos es 16x16). Usted puede agregar iconos a su proyecto usando la Vista de Recursos. |
Different.h |
... class Different: public Win::Window { public: Different() { ::SetRectEmpty(&rectWindow); ::SetRectEmpty(&rectOldWindow); ::SetRectEmpty(&rectOldClientArea); isWindowActive = false; } ~Different() { } CG::Region regionWindow; void RepaintNonClientArea(); void DrawNonClientArea(CG::Gdi& gdi); Win::TitleBarButton buttonWindowIcon; Win::TitleBarButton buttonClock; Win::TitleBarButton buttonMinimize; Win::TitleBarButton buttonMaximize; Win::TitleBarButton buttonClose; // RECT rectWindow; RECT rectOldWindow; RECT rectOldClientArea; // bool isWindowActive; ... }; |
Different.cpp |
... void Different::Window_Open(Win::Event& e) { } void Different::Window_NcCalcSize(Win::Event& e) { if (e.wParam == TRUE) { NCCALCSIZE_PARAMS* calcSize_Params = (NCCALCSIZE_PARAMS*)e.lParam; rectWindow = calcSize_Params->rgrc[0]; // the proposed new window coordinates. rectOldWindow = calcSize_Params->rgrc[1]; // the window before it was moved or resized rectOldClientArea = calcSize_Params->rgrc[2]; // window's client area before the window was moved or resized } e.returnValue = ::DefWindowProc(hWnd, WM_NCCALCSIZE, e.wParam, e.lParam); if (e.wParam == TRUE) { //____________________________________________ Close Button const int titleBarHeight = rectOldClientArea.top - rectOldWindow.top; const int windowWidth = rectWindow.right - rectWindow.left; const int buttonWidth = 24; const int buttonHeight = 24; const int padding = (titleBarHeight > buttonHeight) ? (titleBarHeight - buttonHeight) / 2 : 0; RECT rect; rect.right = windowWidth - buttonWidth; rect.left = rect.right - buttonWidth; rect.top = padding; rect.bottom = rect.top + buttonHeight; buttonClose.NcCalcSize(rect); //____________________________________________ Maximize Button ::OffsetRect(&rect, -(buttonWidth+1), 0); buttonMaximize.NcCalcSize(rect); //____________________________________________ Minimize Button ::OffsetRect(&rect, -(buttonWidth+1), 0); buttonMinimize.NcCalcSize(rect); //____________________________________________ Clock Button ::OffsetRect(&rect, -(buttonWidth+1), 0); buttonClock.NcCalcSize(rect); //____________________________________________ Window Icon rect.left = padding; rect.right = rect.left +buttonWidth; buttonWindowIcon.NcCalcSize(rect); } } void Different::Window_NcCreate(Win::Event& e) { buttonWindowIcon.drawButton = false; buttonClock.color = RGB(240, 240, 100); buttonMinimize.color = RGB(200, 230, 200); buttonMaximize.color = RGB(210, 220, 255); buttonClose.color = RGB(220, 190, 190); // buttonWindowIcon.NcCreate(hInstance, IDI_TITLEBAR, IDI_TITLEBAR); buttonClock.NcCreate(hInstance, IDI_CLOCK, IDI_CLOCKD); buttonMinimize.NcCreate(hInstance, IDI_WINDOW_MINIMIZE, IDI_WINDOW_MINIMIZED); buttonMaximize.NcCreate(hInstance, IDI_WINDOW_MAXIMIZE, IDI_WINDOW_MAXIMIZED); buttonClose.NcCreate(hInstance, IDI_WINDOW_CLOSE, IDI_WINDOW_CLOSED); e.returnValue = ::DefWindowProc(hWnd, WM_NCCREATE, e.wParam, e.lParam); } void Different::Window_NcActivate(Win::Event& e) { isWindowActive = (e.wParam != FALSE); if (e.wParam == TRUE) // Windows is activated repaint { RepaintNonClientArea(); } else if (e.wParam == FALSE) // Windows is losing activation { RepaintNonClientArea(); e.returnValue = TRUE; // Let other window be activated } else // e.wParam may take other values besides TRUE and FALSE { e.returnValue = ::DefWindowProc(hWnd, WM_NCACTIVATE, e.wParam, e.lParam); } } void Different::RepaintNonClientArea() { CG::Gdi gdi(hWnd, regionWindow, DCX_WINDOW|DCX_CACHE|DCX_INTERSECTRGN|DCX_LOCKWINDOWUPDATE, false); this->DrawNonClientArea(gdi); } void Different::DrawNonClientArea(CG::Gdi& gdi) { const int titleBarHeight = rectOldClientArea.top - rectOldWindow.top; const int bottomBorderHeight = rectOldWindow.bottom - rectOldClientArea.bottom; const int leftBorderWidth = rectOldClientArea.left - rectOldWindow.left; const int rightBorderWidth = rectOldWindow.right - rectOldClientArea.right; const int windowWidth = rectWindow.right - rectWindow.left; const int windowHeight = rectWindow.bottom - rectWindow.top; CG::Brush brush(isWindowActive ? RGB(190, 255, 190) : RGB(120, 200, 120)); //___________________________________________________ Title Bar CG::DDBitmap bitmap; RECT rcPaint ={0, 0, windowWidth, titleBarHeight}; bitmap.CreateCompatible(hWnd, windowWidth, titleBarHeight); CG::Gdi gdiBitmap(bitmap, rcPaint, false); gdiBitmap.FillRect(0, 0, rectWindow.right - rectWindow.left, titleBarHeight, brush); //___________________________________________________ Buttons buttonWindowIcon.NcPaint(gdiBitmap); buttonClock.NcPaint(gdiBitmap); buttonMinimize.NcPaint(gdiBitmap); buttonMaximize.NcPaint(gdiBitmap); buttonClose.NcPaint(gdiBitmap); //___________________________________________________ Text wchar_t text[64]; const int len = ::GetWindowText(hWnd, text, 64); if (len > 0) { RECT rc; CG::Font font; font.Create(L"Arial", titleBarHeight/2, false, false, 0); gdiBitmap.Select(font); buttonWindowIcon.GetRect(rc); SIZE size; gdiBitmap.GetTextExtentPoint32W(text, size); gdiBitmap.SetBkMode(true); gdiBitmap.TextOut(rc.right + 10, (titleBarHeight-size.cy)/2, text); } gdi.DrawCompatibleBitmap(bitmap, 0, 0); //___________________________________________________ Left Border gdi.FillRect(0, titleBarHeight, leftBorderWidth, windowHeight - bottomBorderHeight, brush); //___________________________________________________ Right Border gdi.FillRect(windowWidth - rightBorderWidth, titleBarHeight, windowWidth, windowHeight - bottomBorderHeight, brush); //___________________________________________________ Bottom Border gdi.FillRect(0, windowHeight - bottomBorderHeight, windowWidth, windowHeight, brush); } void Different::Window_NcPaint(Win::Event& e) { if (e.wParam == 1) { regionWindow.CreateRect(rectWindow); } else { regionWindow.Set((HRGN)e.wParam); } CG::Gdi gdi(hWnd, regionWindow, DCX_WINDOW|DCX_CACHE|DCX_INTERSECTRGN|DCX_LOCKWINDOWUPDATE, false); this->DrawNonClientArea(gdi); ::RedrawWindow(hWnd, &rectWindow, regionWindow.GetHRGN(), RDW_UPDATENOW); } void Different::Window_NcHitTest(Win::Event& e) { //const int xPos = GET_X_LPARAM(e.lParam); //const int yPos = GET_Y_LPARAM(e.lParam); LRESULT result = ::DefWindowProc(hWnd, WM_NCHITTEST, e.wParam, e.lParam); switch (result) { // Disable drawing and clicks on this buttons case HTCLOSE: case HTMAXBUTTON: case HTMINBUTTON: //case HTSYSMENU: //case HTNOWHERE: //case HTHELP: case HTERROR: e.returnValue = HTCAPTION; return; default: e.returnValue = result; } } void Different::Window_NcLButtonDown(Win::Event& e) { if (buttonClock.NcLButtonDown(hWnd, e, regionWindow)) return; if (buttonMinimize.NcLButtonDown(hWnd, e, regionWindow)) return; if (buttonMaximize.NcLButtonDown(hWnd, e, regionWindow)) return; if (buttonClose.NcLButtonDown(hWnd, e, regionWindow)) return; e.returnValue = ::DefWindowProc(hWnd, WM_NCLBUTTONDOWN, e.wParam, e.lParam); } void Different::Window_NcMouseMove(Win::Event& e) { buttonClock.NcMouseMove(hWnd, e, regionWindow); buttonMinimize.NcMouseMove(hWnd, e, regionWindow); buttonMaximize.NcMouseMove(hWnd, e, regionWindow); buttonClose.NcMouseMove(hWnd, e, regionWindow); e.returnValue = ::DefWindowProc(hWnd, WM_NCMOUSEMOVE, e.wParam, e.lParam); } void Different::Window_NcLButtonUp(Win::Event& e) { if (buttonClock.NcLButtonUp(hWnd, e, regionWindow)) { ::SendMessage(hWnd, WM_COMMAND, MAKEWPARAM(IDM_CLOCK, 0), 0); } else if (buttonMinimize.NcLButtonUp(hWnd, e, regionWindow)) { ::SendMessage(hWnd, WM_SYSCOMMAND, SC_MINIMIZE, 0); RepaintNonClientArea(); } else if (buttonMaximize.NcLButtonUp(hWnd, e, regionWindow)) { WINDOWPLACEMENT windowPlacement; windowPlacement.length = sizeof(WINDOWPLACEMENT); if (::GetWindowPlacement(hWnd, &windowPlacement) == 0) { ::SendMessage(hWnd, WM_SYSCOMMAND, SC_RESTORE, 0); RepaintNonClientArea(); ::InvalidateRect(hWnd, NULL, TRUE); } else { if (windowPlacement.showCmd == SW_MAXIMIZE) { ::SendMessage(hWnd, WM_SYSCOMMAND, SC_RESTORE, 0); RepaintNonClientArea(); ::InvalidateRect(hWnd, NULL, TRUE); } else { ::SendMessage(hWnd, WM_SYSCOMMAND, SC_MAXIMIZE, 0); RepaintNonClientArea(); ::InvalidateRect(hWnd, NULL, TRUE); } } } else if (buttonClose.NcLButtonUp(hWnd, e, regionWindow)) { ::SendMessage(hWnd, WM_SYSCOMMAND, SC_CLOSE, 0); } else { e.returnValue = ::DefWindowProc(hWnd, WM_NCLBUTTONUP, e.wParam, e.lParam); } } void Different::Cmd_Clock(Win::Event& e) { this->MessageBox(L"Clock", L"Different", MB_OK); } void Different::Window_Paint(Win::Event& e) { CG::Gdi gdi(hWnd, true, false); RECT rc = {0, 0, width, height}; gdi.TextOutCenter(rc, L"Hello", true, true); } |
Problem 2 |
Create a Wintempla Window Application called Cross. After creating the program, open Wintempla and check the following events in the main window: NcActivate, NcCalcSize, NcCreate, NcHitTest, NcLButtonDown, NcLButtonUp, NcMouseMove, NcPaint. For this problem you will need one icon (clock.ico). You can add icons to your project using the Resource View. You need to edit the targetver.h file to make the project compatible with Microsoft Windows Vista or later. Cree una Aplicación de Ventana de Wintempla llamada Cross. Después de crear el programa, abra Wintempla y marque los siguientes eventos en la ventana principal: NcActivate, NcCalcSize, NcCreate, NcHitTest, NcLButtonDown, NcLButtonUp, NcMouseMove, NcPaint. Para este problema usted necesitará de un icono (clock.ico). Usted puede agregar iconos a su proyecto usando la Vista de Recursos. Usted necesita editar el archivo targetver.h para hacer el proyecto compatible con Microsoft Windows Vista o posterior. |
Cross.h |
... class Cross: public Win::Window { public: Cross() { } ~Cross() { } CG::Region regionWindow; Win::TitleBarButton buttonClock; ... }; |
Cross.cpp |
... void Cross::Window_Open(Win::Event& e) { } void Cross::Window_NcCalcSize(Win::Event& e) { if (e.wParam == TRUE) { NCCALCSIZE_PARAMS* calcSize_Params = (NCCALCSIZE_PARAMS*)e.lParam; RECT rectWindow = calcSize_Params->rgrc[0]; // the proposed new window coordinates. RECT rectOldWindow = calcSize_Params->rgrc[1]; // the window before it was moved or resized RECT rectOldClientArea = calcSize_Params->rgrc[2]; // window's client area before the window was moved or resized const int titleBarHeight = rectOldClientArea.top - rectOldWindow.top; regionWindow.CreateRect(rectWindow); //_______________________________________ Get TitleBarInfo (Edit the targetver.h to activate Windows 6 Vista) TITLEBARINFOEX titleBarInfo; titleBarInfo.cbSize = sizeof(TITLEBARINFOEX); ::SendMessage(hWnd, WM_GETTITLEBARINFOEX, 0, (LPARAM)&titleBarInfo); RECT rectMinimizeButton = titleBarInfo.rgrect[2]; // Screen coordinates ::OffsetRect(&rectMinimizeButton, -rectWindow.left, -rectWindow.top); // Window coordinates const int buttonSize = titleBarHeight - 6; RECT rectButton; rectButton.right = rectMinimizeButton.left - 4; rectButton.left = rectButton.right - buttonSize; rectButton.top = (titleBarHeight - buttonSize)/2; rectButton.bottom = rectButton.top + buttonSize; buttonClock.NcCalcSize(rectButton); } e.returnValue = ::DefWindowProc(hWnd, WM_NCCALCSIZE, e.wParam, e.lParam); } void Cross::Window_NcCreate(Win::Event& e) { buttonClock.NcCreate(hInstance, IDI_CLOCK, IDI_CLOCKD); e.returnValue = ::DefWindowProc(hWnd, WM_NCCREATE, e.wParam, e.lParam); } void Cross::Window_NcHitTest(Win::Event& e) { RECT rectButton; buttonClock.GetRect(rectButton); RECT rcWindow; ::GetWindowRect(hWnd, &rcWindow); POINT point; point.x = GET_X_LPARAM(e.lParam) - rcWindow.left; point.y = GET_Y_LPARAM(e.lParam) - rcWindow.top; if (PtInRect(&rectButton, point)) { e.returnValue = HTBORDER; return; } e.returnValue = ::DefWindowProc(hWnd, WM_NCHITTEST, e.wParam, e.lParam); } void Cross::Window_NcLButtonDown(Win::Event& e) { buttonClock.NcLButtonDown(hWnd, e, regionWindow); e.returnValue = ::DefWindowProc(hWnd, WM_NCLBUTTONDOWN, e.wParam, e.lParam); } void Cross::Window_NcLButtonUp(Win::Event& e) { if (buttonClock.NcLButtonUp(hWnd, e, regionWindow)) { ::SendMessage(hWnd, WM_COMMAND, MAKEWPARAM(IDM_CLOCK, 0), 0); } else { e.returnValue = ::DefWindowProc(hWnd, WM_NCLBUTTONUP, e.wParam, e.lParam); } } void Cross::Window_NcMouseMove(Win::Event& e) { buttonClock.NcMouseMove(hWnd, e, regionWindow); e.returnValue = ::DefWindowProc(hWnd, WM_NCMOUSEMOVE, e.wParam, e.lParam); } void Cross::Window_NcPaint(Win::Event& e) { RECT rectWindow; ::GetWindowRect(hWnd, &rectWindow); HRGN hRgn = NULL; if (e.wParam == 1) hRgn = ::CreateRectRgnIndirect(&rectWindow); else hRgn = (HRGN)e.wParam; //___________________________________________________ Exclude the button from the painting region RECT rcButton; buttonClock.GetRect(rcButton); ::OffsetRect(&rcButton, rectWindow.left, rectWindow.top); // Convert to screen coordinates HRGN rgnButton = ::CreateRectRgnIndirect(&rcButton); ::CombineRgn(hRgn, hRgn, rgnButton, RGN_XOR); ::DeleteObject(rgnButton); e.returnValue = ::DefWindowProc(hWnd, WM_NCPAINT, (WPARAM)hRgn, 0); //___________________________________________________ Draw The Button buttonClock.Redraw(hWnd, regionWindow); } void Cross::Window_NcActivate(Win::Event& e) { e.returnValue = ::DefWindowProc(hWnd, WM_NCACTIVATE, e.wParam, e.lParam); buttonClock.Redraw(hWnd, regionWindow); } void Cross::Cmd_Clock(Win::Event& e) { this->MessageBox(L"Clock", L"Cross", MB_OK); } |